我们在前面的文章分析了Vertx核心单机部分的源码。今天轮到Vert.x-Web,由于Web的内容比较多,因此分为上下两部分。
- 上:分析Vert.x-Web核心类及其主要能力。
- 下:分析HttpServer的线程调度以及Web OpenAPI的工作原理
一个基本的VertxWeb代码片段如下:
1 2 3 4 5 6 7 8 9 10 11 12
| fun main() { val vertx = Vertx.vertx() val router = Router.router(vertx) router.get("/hello") .handler { ctx -> println("哈哈哈,我好开心") ctx.next() } .failureHandler(ErrorHandler.create()) val server = vertx.createHttpServer().requestHandler(router) server.listen(8080) }
|
上面创建了一个Http服务,暴露8080端口,并注册了一个path为hello,方法为GET的接口。上面涉及到Vert.x-Web的类有:Router、Route、RoutingContext、HttpServer。我们介绍前三个。
Router
Router
首先是Router接口,它主要有以下几个方面组成。
Router.router(vertx) 创建一个Router对象
routeXXX() 各种路由方法,可以根据路径、方法、媒体类型、正则表达式等各种方式路由,返回一个Route对象,用于指定处理器。
getRoutes() 获取所有route
clear() 清除所有Route
mountSubRouter() 挂载子router
exceptionHandler() 指定一个router级别的异常处理器,当handler中抛出异常时,它会捕获。但它不会影响正常业务逻辑。
但它目前已被废弃,应该使用errorHandler(),它对应的是500的错误
errorHandler(code, handler) 当发生特定错误码时会调用它。当RoutingContext失败(fail) 或 一些handler失败但没有写响应 或 handler中抛出了异常,都会调用它。需要注意的是它的500有特殊的意义,代表通用错误。
handleContext(context) 将一个RoutingContext传进来,当一个Router被挂载到某个route时会用:将该route的RoutingContext传入本router进行处理。
handleFailure(RoutingContext) 使用场景同上,处理失败
modifiedHandler(handler) 当本Router的Route发生变化时该方法会被触发。
首先说,Router只是Handler的子类,因此本质上还只是一个处理器,是被动调用的。
1 2 3
| @VertxGen public interface Router extends Handler<HttpServerRequest> { ... ...
|
RouterImpl
众多route生成方法的实现都大同小异,都是创建RouteImpl
1 2 3 4
| public synchronized Route route(String path) { state = state.incrementOrderSequence(); return new RouteImpl(this, state.getOrderSequence(), path); }
|
另外一个重点是handle,既然Router是Handler的子类,因此本类最初被调用的一定是handle方法(在请求进来时调用)
1 2 3 4 5
| @Override public void handle(HttpServerRequest request) { new RoutingContextImpl(null, this, request, state.getRoutes()).next(); }
|
啥也没干,就创建了一个RoutingContextImpl,并调用了next(),开启处理逻辑。
RouterState
一个RouterImpl包含一个RouterState,在初始化时创建,用于管理Router的状态
1 2 3 4
| public RouterImpl(Vertx vertx) { this.vertx = vertx; this.state = new RouterState(this); }
|
共有如下几种状态
- Route集合:Router内注册的Route全在这里
- errorHandler映射:失败的处理器,其中key对应的错误码,如400
- modifiedHandler:用于在Router发生变化时做出响应的处理器
1 2 3
| private final Set<RouteImpl> routes; private final Map<Integer, Handler<RoutingContext>> errorHandlers; private final Handler<Router> modifiedHandler;
|
Route
Route
一个接口,对路由信息的描述,包含如下几种信息,见名思义。
- method
- path
- regex
- consumes
- produces
同时包含了对匹配到的请求的处理方式
- handler():为Route添加一个处理器,多个handler之间会连缀调用。
- failureHandler():为Route添加一个失败处理器,多个handler之间会连缀调用。
- blockingHandler():为Route添加一个能够执行阻塞操作的处理器。
- subRouter():为Route添加一个子Router,符合要求的请求都会被转发给该Router。
RouteImpl
就是对上面各个方法的实现,这里不赘述。
RouteState
对一个路由状态的持有,相对RouterState而言,它持有的状态复杂得多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| private final String path;
private final int order;
private final boolean enabled;
private final Set<HttpMethod> methods;
private final Set<MIMEHeader> consumes;
private final boolean emptyBodyPermittedWithConsumes;
private final Set<MIMEHeader> produces;
private final List<Handler<RoutingContext>> contextHandlers;
private final List<Handler<RoutingContext>> failureHandlers;
private final boolean added;
private final Pattern pattern;
private final List<String> groups;
private final boolean useNormalisedPath;
private final Set<String> namedGroupsInRegex;
private final Pattern virtualHostPattern;
private final boolean pathEndsWithSlash;
private final boolean exclusive;
private final boolean exactPath;
|
对Route最重要的方法当然是判断是否匹配,该方法也是在RouteState中给出
io.vertx.ext.web.impl.RouteState#matches,逻辑比较复杂,有兴趣可以去看看
RoutingContext
RoutingContext是Router上下文的抽象,代表一个请求从进入到响应全阶段的状态。由于在Router.handle()中,直接创建了RoutingContextImpl对象,并调用了RoutingContext.next(),因此我们重点关注它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public RoutingContextImpl(String mountPoint, RouterImpl router, HttpServerRequest request, Set<RouteImpl> routes) { super(mountPoint, request, routes); this.router = router; this.fillParsedHeaders(request); if (request.path().length() == 0) { this.fail(400); } else if (request.path().charAt(0) != '/') { this.fail(404); } }
RoutingContextImplBase(String mountPoint, HttpServerRequest request, Set<RouteImpl> routes) { this.mountPoint = mountPoint; this.request = new HttpServerRequestWrapper(request); this.routes = routes; this.iter = routes.iterator(); this.currentRouteNextHandlerIndex = new AtomicInteger(0); this.currentRouteNextFailureHandlerIndex = new AtomicInteger(0); this.resetMatchFailure(); }
public void next() { if (!this.iterateNext()) { this.checkHandleNoMatch(); } }
|
仔细看iterateNext()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| boolean iterateNext() { boolean failed = this.failed(); if (this.currentRoute != null) { try { if (!failed && this.currentRoute.hasNextContextHandler(this)) { this.currentRouteNextHandlerIndex.incrementAndGet(); this.resetMatchFailure(); this.currentRoute.handleContext(this); return true; }
if (failed && this.currentRoute.hasNextFailureHandler(this)) { this.currentRouteNextFailureHandlerIndex.incrementAndGet(); this.currentRoute.handleFailure(this); return true; } } catch (Throwable var5) { this.handleInHandlerRuntimeFailure(this.currentRoute.getRouter(), failed, var5); return true; } }
while(true) { if (this.iter.hasNext()) { RouteState routeState = ((RouteImpl)this.iter.next()).state(); this.currentRouteNextHandlerIndex.set(0); this.currentRouteNextFailureHandlerIndex.set(0);
try { int matchResult = routeState.matches(this, this.mountPoint(), failed); if (matchResult != 0) { if (matchResult == 405) { if (this.matchFailure == 404) { this.matchFailure = matchResult; } } else if (matchResult != 404) { this.matchFailure = matchResult; } continue; }
this.resetMatchFailure();
try { this.currentRoute = routeState;、
if (failed && this.currentRoute.hasNextFailureHandler(this)) { this.currentRouteNextFailureHandlerIndex.incrementAndGet(); routeState.handleFailure(this); } else { if (!this.currentRoute.hasNextContextHandler(this)) { continue; }
this.currentRouteNextHandlerIndex.incrementAndGet(); routeState.handleContext(this); } } catch (Throwable var6) { this.handleInHandlerRuntimeFailure(routeState.getRouter(), failed, var6); }
return true; } catch (Throwable var7) { if (LOG.isTraceEnabled()) { LOG.trace("IllegalArgumentException thrown during iteration", var7); }
if (!this.response().ended()) { this.unhandledFailure(var7 instanceof IllegalArgumentException ? 400 : -1, var7, routeState.getRouter()); }
return true; } }
return false; } }
|
再来仔细看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private void checkHandleNoMatch() { if (this.failed()) { this.unhandledFailure(this.statusCode, this.failure, this.router); } else { Handler<RoutingContext> handler = this.router.getErrorHandlerByStatusCode(this.matchFailure); this.statusCode = this.matchFailure; if (handler == null) { this.response().setStatusCode(this.matchFailure); if (this.request().method() != HttpMethod.HEAD && this.matchFailure == 404) { this.response().putHeader(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8").end("<html><body><h1>Resource not found</h1></body></html>"); } else { this.response().end(); } } else { handler.handle(this); } }
}
|
总结
到现在,抛开Router是被谁调用这问题。我们知道整个RoutingContext的执行起始是Router的handle方法:它创建RoutingContextImpl对象,并调用next()启动route的匹配操作,并在匹配后调用对应route的正常handler链或失败handler链。
这里掌握三个关键点
- RoutingContext对象是在Router的handle中创建的,并在匹配到的route的handler中流转。
- 遍历所有route的过程会在每次请求进来被处理。逻辑在iterateNext()方法的下半部分。
- route的handler链调用比较特殊,需要开发者手动调用RoutingContext的next()方法处理。逻辑在iterateNext()方法的上半部分。
- 在从Router的handle开始,都是在当前线程不阻塞地执行,可以一镜到底。
对于Web的整个处理链,我们弄得差不多了。现在还存在如下问题,我们留待下篇再来看。
- 谁构建HttpServerRequest并调用了Router的handler?
- 调用Router时线程分配是如何的?